import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import org.junit.Assert;

import data.CompoundContainer;
import data.Constants;

public abstract class SerializationTest {
	protected TestRecorder recorder;
	protected Storage storage;
	protected String technique = "N/A";

	// convenience
	protected static long startTime;
	protected static int iterations = Constants.ITERATIONS;

	protected boolean boolVal;
	protected boolean[] boolVals;
	protected byte byteVal;
	protected byte[] byteVals;
	protected short shortVal;
	protected short[] shortVals;
	protected int intVal;
	protected int[] intVals;
	protected long longVal;
	protected long[] longVals;
	protected float floatVal;
	protected float[] floatVals;
	protected double doubleVal;
	protected double[] doubleVals;
	protected String stringVal;
	protected String[] stringVals;
	protected CompoundContainer compoundVal;
	protected CompoundContainer[] compoundVals;

	public SerializationTest(TestRecorder recorder, Storage storage) {
		this.recorder = recorder;
		this.storage = storage;

		boolVal = true;
		boolVals = new boolean[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			boolVals[i] = i % 2 == 0;
		}

		byteVal = (byte) 0x88;
		byteVals = new byte[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			byteVals[i] = (byte) i;
		}

		shortVal = Short.MAX_VALUE / 2;
		shortVals = new short[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			shortVals[i] = (short) (i + Short.MAX_VALUE / 2);
		}

		intVal = Integer.MAX_VALUE / 2;
		intVals = new int[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			intVals[i] = (int) (i + Integer.MAX_VALUE / 2);
		}

		longVal = Long.MAX_VALUE / 2;
		longVals = new long[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			longVals[i] = (int) (i + Long.MAX_VALUE / 2);
		}

		floatVal = (Float.MAX_VALUE / 2) / 1000f;
		floatVals = new float[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			floatVals[i] = i / 1000f + (Float.MAX_VALUE / 2);
		}

		doubleVal = (Double.MAX_VALUE / 2) / 1000d;
		doubleVals = new double[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			doubleVals[i] = i / 1000d + (Double.MAX_VALUE / 2);
		}

		stringVal = new String();
		char ch = 0;
		for (int i = 0; i < Constants.STRING_LENGTH; i++) {
			while (!Character.isAlphabetic((ch))) {
				++ch;
			}
			stringVal += ch++;
		}
		stringVals = new String[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			stringVals[i] = stringVal;
		}

		compoundVal = new CompoundContainer();
		compoundVal.booleanvalue = boolVal;
		compoundVal.bytevalue = byteVal;
		compoundVal.doublevalue = doubleVal;
		compoundVal.floatvalue = floatVal;
		compoundVal.intvalue = intVal;
		compoundVal.longvalue = longVal;
		compoundVal.shortvalue = shortVal;
		compoundVal.stringvalue = stringVal;

		compoundVal.compoundvalue = new CompoundContainer(compoundVal);
		compoundVal.compoundvalue.compoundvalue = new CompoundContainer(
				compoundVal.compoundvalue);

		compoundVals = new CompoundContainer[Constants.ITERATIONS];
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			compoundVals[i] = new CompoundContainer(compoundVal);
		}

	}

	protected void write(byte[] content, OutputStream os) throws IOException {
		os.write(content);
	}

	protected byte[] read(InputStream is) throws IOException {
		int available = is.available();
		byte[] content = new byte[available];
		int read = is.read(content);
		if (read != available) {
			is.close();
			throw new RuntimeException("Urghs!");
		}
		return content;
	}

	protected abstract void testBoolWrite(boolean value, OutputStream os)
			throws Exception;

	protected abstract void testBoolWrite(boolean[] value, OutputStream os)
			throws Exception;

	protected abstract boolean testBoolRead(InputStream is) throws Exception;

	protected abstract boolean[] testBoolsRead(InputStream is) throws Exception;

	protected abstract void testByteWrite(byte value, OutputStream os)
			throws Exception;

	protected abstract void testByteWrite(byte[] value, OutputStream os)
			throws Exception;

	protected abstract byte testByteRead(InputStream is) throws Exception;

	protected abstract byte[] testBytesRead(InputStream is) throws Exception;

	protected abstract void testShortWrite(short value, OutputStream os)
			throws Exception;

	protected abstract void testShortWrite(short[] value, OutputStream os)
			throws Exception;

	protected abstract short testShortRead(InputStream is) throws Exception;

	protected abstract short[] testShortsRead(InputStream is) throws Exception;

	protected abstract void testIntWrite(int value, OutputStream os)
			throws Exception;

	protected abstract void testIntWrite(int[] value, OutputStream os)
			throws Exception;

	protected abstract int testIntRead(InputStream is) throws Exception;

	protected abstract int[] testIntsRead(InputStream is) throws Exception;

	protected abstract void testLongWrite(long value, OutputStream os)
			throws Exception;

	protected abstract void testLongWrite(long[] value, OutputStream os)
			throws Exception;

	protected abstract long testLongRead(InputStream is) throws Exception;

	protected abstract long[] testLongsRead(InputStream is) throws Exception;

	protected abstract void testFloatWrite(float value, OutputStream os)
			throws Exception;

	protected abstract void testFloatWrite(float[] value, OutputStream os)
			throws Exception;

	protected abstract float testFloatRead(InputStream is) throws Exception;

	protected abstract float[] testFloatsRead(InputStream is) throws Exception;

	protected abstract void testDoubleWrite(double value, OutputStream os)
			throws Exception;

	protected abstract void testDoubleWrite(double[] value, OutputStream os)
			throws Exception;

	protected abstract double testDoubleRead(InputStream is) throws Exception;

	protected abstract double[] testDoublesRead(InputStream is)
			throws Exception;

	protected abstract void testStringWrite(String value, OutputStream os)
			throws Exception;

	protected abstract void testStringWrite(String[] value, OutputStream os)
			throws Exception;

	protected abstract String testStringRead(InputStream is) throws Exception;

	protected abstract String[] testStringsRead(InputStream is)
			throws Exception;

	protected abstract void testCompoundWrite(CompoundContainer value,
			OutputStream os) throws Exception;

	protected abstract void testCompoundWrite(CompoundContainer[] value,
			OutputStream os) throws Exception;

	protected abstract CompoundContainer testCompoundRead(InputStream is)
			throws Exception;

	protected abstract CompoundContainer[] testCompoundsRead(InputStream is)
			throws Exception;

	protected void performTests(boolean warmup) throws Exception {
		
		testBool(warmup);
		testByte(warmup);
		testShort(warmup);
		testInt(warmup);
		testLong(warmup);
		testFloat(warmup);
		testDouble(warmup);
		testString(warmup);
		testCompound(warmup);

	}

	private void testBool(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testBoolWrite(boolVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing bool for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.BOOL, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				boolean ret = testBoolRead(storage.getInputStream(i));
				Assert.assertEquals(ret, boolVal);
			} catch (Exception e) {
				System.out.println("error reading bool for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.BOOL, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.BOOL, storage.bitLength(0) / 1f);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testBoolWrite(boolVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing bools for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.BOOL, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			boolean[] ret = testBoolsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, boolVals));
		} catch (Exception e) {
			System.out.println("error reading bools for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.BOOL, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.BOOL, storage.bitLength(0)
							/ (float) boolVals.length);
		}
	}

	
	private void testByte(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testByteWrite(byteVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing byte for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.BYTE, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				byte ret = testByteRead(storage.getInputStream(i));
				Assert.assertEquals(ret, byteVal);
			} catch (Exception e) {
				System.out.println("error reading byte for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.BYTE, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.BYTE, storage.bitLength(0) / (float)Byte.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testByteWrite(byteVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing bytes for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.BYTE, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			byte[] ret = testBytesRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, byteVals));
		} catch (Exception e) {
			System.out.println("error reading bytes for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.BYTE, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.BYTE, storage.bitLength(0)
							/ (float) (byteVals.length * Byte.SIZE));
		}
	}

	
	private void testShort(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testShortWrite(shortVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing short for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.SHORT, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				short ret = testShortRead(storage.getInputStream(i));
				Assert.assertEquals(ret, shortVal);
			} catch (Exception e) {
				System.out.println("error reading short for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.SHORT, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.SHORT, storage.bitLength(0) / (float)Short.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testShortWrite(shortVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing shorts for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.SHORT, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			short[] ret = testShortsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, shortVals));
		} catch (Exception e) {
			System.out.println("error reading shorts for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.SHORT, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.SHORT, storage.bitLength(0)
							/ (float) (shortVals.length * Short.SIZE));
		}
	}

	
	private void testInt(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testIntWrite(intVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing int for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.INTEGER, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				int ret = testIntRead(storage.getInputStream(i));
				Assert.assertEquals(ret, intVal);
			} catch (Exception e) {
				System.out.println("error reading int for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.INTEGER, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.INTEGER, storage.bitLength(0) / (float)Integer.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testIntWrite(intVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing ints for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.INTEGER, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			int[] ret = testIntsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, intVals));
		} catch (Exception e) {
			System.out.println("error reading ints for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.INTEGER, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.INTEGER, storage.bitLength(0)
							/ (float) (intVals.length * Integer.SIZE));
		}
	}

	private void testLong(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testLongWrite(longVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing long for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.LONG, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				long ret = testLongRead(storage.getInputStream(i));
				Assert.assertEquals(ret, longVal);
			} catch (Exception e) {
				System.out.println("error reading long for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.LONG, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.LONG, storage.bitLength(0) / (float)Long.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testLongWrite(longVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing longs for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.LONG, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			long[] ret = testLongsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, longVals));
		} catch (Exception e) {
			System.out.println("error reading longs for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.LONG, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.LONG, storage.bitLength(0)
							/ (float) (longVals.length * Long.SIZE));
		}
	}

	private void testFloat(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		float duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testFloatWrite(floatVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing float for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.FLOAT, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				float ret = testFloatRead(storage.getInputStream(i));
				Assert.assertEquals(ret, floatVal, .0001f);
			} catch (Exception e) {
				System.out.println("error reading float for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.FLOAT, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.FLOAT, storage.bitLength(0) / (float)Float.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testFloatWrite(floatVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing floats for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.FLOAT, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			float[] ret = testFloatsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, floatVals));
		} catch (Exception e) {
			System.out.println("error reading floats for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.FLOAT, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.FLOAT, storage.bitLength(0)
							/ (float) (floatVals.length * Float.SIZE));
		}
	}

	
	private void testDouble(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		double duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testDoubleWrite(doubleVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing double for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.DOUBLE, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				double ret = testDoubleRead(storage.getInputStream(i));
				Assert.assertEquals(ret, doubleVal, .0001f);
			} catch (Exception e) {
				System.out.println("error reading double for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.DOUBLE, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.DOUBLE, storage.bitLength(0) / (float)Double.SIZE);
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testDoubleWrite(doubleVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing doubles for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.DOUBLE, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			double[] ret = testDoublesRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, doubleVals));
		} catch (Exception e) {
			System.out.println("error reading doubles for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.DOUBLE, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.DOUBLE, storage.bitLength(0)
							/ (float) (doubleVals.length * Double.SIZE));
		}
	}

	
	private void testString(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testStringWrite(stringVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing string for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.STRING, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				String ret = testStringRead(storage.getInputStream(i));
				Assert.assertTrue(stringVal.compareTo(ret) == 0);
			} catch (Exception e) {
				System.out.println("error reading string for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.STRING, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.STRING, storage.bitLength(0) / (float) (stringVal.length() * 16));
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testStringWrite(stringVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing strings for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.STRING, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			String[] ret = testStringsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, stringVals));
		} catch (Exception e) {
			System.out.println("error reading strings for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.STRING, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.STRING, storage.bitLength(0)
							/ (float) (stringVals.length * stringVal.length() * 16));
		}
	}


	private void testCompound(boolean warmup) throws IOException {
		boolean writeCompleted;
		boolean readCompleted;
		long duration;

		// ATOMS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				testCompoundWrite(compoundVal, storage.getOutputStream(i));
			} catch (Exception e) {
				System.out.println("error writing compound for " + technique + " " + e.getMessage());
				writeCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE,
					TestRecorder.DataType.COMPOUND, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		for (int i = 0; i < Constants.ITERATIONS; i++) {
			try {
				CompoundContainer ret = testCompoundRead(storage.getInputStream(i));
				Assert.assertEquals(ret, compoundVal);
			} catch (Exception e) {
				System.out.println("error reading compound for " + technique + " " + e.getMessage());
				readCompleted = false;
				break;
			}
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ,
					TestRecorder.DataType.COMPOUND, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO,
					TestRecorder.DataType.COMPOUND, storage.bitLength(0) / (float)CompoundContainer.getSize());
		}

		// ARRAYS
		storage.reset();
		startTime = System.nanoTime();
		writeCompleted = true;
		try {
			testCompoundWrite(compoundVals, storage.getOutputStream(0));
		} catch (Exception e) {
			System.out.println("error writing compounds for " + technique + " " + e.getMessage());
			writeCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (writeCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.WRITE_ARRAY,
					TestRecorder.DataType.COMPOUND, duration);
		}

		startTime = System.nanoTime();
		readCompleted = true;
		try {
			CompoundContainer[] ret = testCompoundsRead(storage.getInputStream(0));
			Assert.assertTrue(Arrays.equals(ret, compoundVals));
		} catch (Exception e) {
			System.out.println("error reading compounds for " + technique + " " + e.getMessage());
			readCompleted = false;
		}
		duration = System.nanoTime() - startTime;

		if (readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.READ_ARRAY,
					TestRecorder.DataType.COMPOUND, duration);
		}

		if (writeCompleted && readCompleted && !warmup) {
			recorder.record(technique, TestRecorder.Operation.RATIO_ARRAY,
					TestRecorder.DataType.COMPOUND, storage.bitLength(0)
							/ (float) (compoundVals.length * (float)CompoundContainer.getSize()));
		}
	}

	
	public void runTests() throws Exception {

		System.out.print("Warming up " + technique + " ...");
		System.out.flush();
		performTests(true);
		performTests(true);
		performTests(true);
		System.out.println(" done. Hot enough!");
		long testStartTime = System.nanoTime();
		for (int i = 0; i < 20; i++) {
			performTests(false);
		}
		System.out.println("\t" + (System.nanoTime() - testStartTime) / 1000000 + "ms in total");
	}
}
